package qcloud

import (
    "fmt"
    "gitee.com/zwx784533/zwx2018/config"
    "time"
    "hash"
    "crypto/sha1"
    "crypto/hmac"
    "encoding/base64"
    "math/rand"
    "log"
    "net/http"
    "io/ioutil"
    "strings"
    "sort"
    "net/url"
    "encoding/json"
)

func GetQcloudSign() string  {
    qcloudConfig := config.QcloudConf
    curTime := time.Now().Second()
    randTime := fmt.Sprintf("%10v", rand.New(rand.NewSource(time.Now().UnixNano())))
    fileId := fmt.Sprintf("/%d/%s/", qcloudConfig.AppId, qcloudConfig.Bucket)
    original := fmt.Sprintf("a=%d&b=%s&k=%s&e=%d&t=%d&r=%d&f=%s",
        qcloudConfig.AppId, qcloudConfig.Bucket, qcloudConfig.SecretId,
        qcloudConfig.DurationSeconds, curTime, randTime, fileId)
    signTmp := CalSignature(qcloudConfig.SecretKey, original)
    sign := fmt.Sprintf("%s%s", signTmp, original)
    sign = base64.StdEncoding.EncodeToString([]byte(sign))
    return sign
}

// calSignature 计算 Signature
func CalSignature(signKey, stringToSign string) string {
    digest := CalHMACDigest(signKey, stringToSign, "sha1")
    return fmt.Sprintf("%x", digest)
}

// HMAC 签名
func CalHMACDigest(key, msg, signMethod string) []byte {
    var hashFunc func() hash.Hash
    switch signMethod {
    case "sha1":
        hashFunc = sha1.New
    default:
        hashFunc = sha1.New
    }
    h := hmac.New(hashFunc, []byte(key))
    h.Write([]byte(msg))
    return h.Sum(nil)
}

func makePlainText(requestMethod string, requestHost string, requestPath string, params map[string]interface{}) (plainText string, err error) {

    plainText += strings.ToUpper(requestMethod)
    plainText += requestHost
    plainText += requestPath
    plainText += "?"

    // 排序
    keys := make([]string, 0, len(params))
    for k, _ := range params {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    var plainParms string
    for i := range keys {
        k := keys[i]
        plainParms += "&" + fmt.Sprintf("%v", k) + "=" + fmt.Sprintf("%v", params[k])
    }
    plainText += plainParms[1:]

    return plainText, nil
}

func sign(requestMethod string, requestHost string, requestPath string, params map[string]interface{}, secretKey string) (sign string, err error) {

    var source string

    source, err = makePlainText(requestMethod, requestHost, requestPath, params)
    if err != nil {
        panic(err)
        log.Fatalln("Make PlainText error.", err)
        return sign, err
    }

    hmacObj := hmac.New(sha1.New, []byte(secretKey))
    hmacObj.Write([]byte(source))

    sign = base64.StdEncoding.EncodeToString(hmacObj.Sum(nil))

    return sign, nil
}

type Credent struct {
    SessionToken string `json:"sessionToken"`
    TmpSecretID  string `json:"tmpSecretId"`
    TmpSecretKey string `json:"tmpSecretKey"`
}

// Data data in sts response body
type Data struct {
    Credentials Credent `json:"credentials"`
}

// Response sts response body
// In qcloud_sign_golang this response only return ak, sk and token
type Response struct {
    Dat Data `json:"data"`
}

func GetTmpSign() (credent *Credent, err error)  {
    qcfg := config.QcloudConf
    cfg := map[string]interface{}{"secretId": qcfg.SecretId, "secretKey": qcfg.SecretKey}
    policy := fmt.Sprintf("{\"statement\": [{\"action\": [\"name/cos:GetObject\",\"name/cos:PutObject\"],\"effect\": \"allow\",\"resource\":[\"qcs::cos:%s:uid/%d:prefix//*\"]}],\"version\": \"2.0\"}", qcfg.Region, qcfg.AppId)
    params := map[string]interface{}{"Region": qcfg.Region, "Action": "GetFederationToken", "name": qcfg.Bucket, "policy": policy}
    // 发送请求
    retData, err := sendRequest("sts", params, cfg)
    if err != nil {
        return nil, err
    }
    r := &Response{}
    err = json.Unmarshal([]byte(retData), r)
    if err != nil {
        return nil, err
    }

    return &r.Dat.Credentials, nil

}

func sendRequest(mod string, params map[string]interface{}, config map[string]interface{}) (retData string, err error) {

    secretId := fmt.Sprintf("%s", config["secretId"])
    secretKey := fmt.Sprintf("%s", config["secretKey"])

    requestMethod := "POST"
    requestHost := mod + ".api.qcloud.com"
    requestPath := "/v2/index.php"

    paramValues := url.Values{}
    if params["SecretId"] == nil {
        params["SecretId"] = secretId
    }
    if params["Timestamp"] == nil {
        params["Timestamp"] = fmt.Sprintf("%v", time.Now().Unix())
    }
    if params["Nonce"] == nil {
        rand.Seed(time.Now().UnixNano())
        params["Nonce"] = fmt.Sprintf("%v", rand.Int())
    }
    if params["Region"] == nil {
        params["Region"] = "gz"
    }
    if params["Action"] == nil {
        params["Action"] = "DescribeInstances"
    }

    sign, err := sign(requestMethod, requestHost, requestPath, params, secretKey)
    paramValues.Add("Signature", sign)

    for k, v := range params {
        paramValues.Add(fmt.Sprintf("%v", k), fmt.Sprintf("%v", v))
    }

    urlStr := "https://" + requestHost + requestPath

    rsp, err := http.PostForm(urlStr, paramValues)

    if err != nil {
        panic(err)
        log.Fatal("http post error.", err)
        return "", err
    }

    defer rsp.Body.Close()

    retData_, err := ioutil.ReadAll(rsp.Body)
    if err != err {
        panic(err)
        return "", err
    }

    return string(retData_), nil
}
